/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */

package com.yami.shop.api.controller;

import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.h5.H5Service;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.jsapi.model.SceneInfo;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.model.TransactionAmount;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import com.yami.shop.api.config.WeixinConfig;
import com.yami.shop.api.utils.HttpUtils;
import com.yami.shop.api.utils.IdGenUtils;
import com.yami.shop.api.utils.WeixinUtils;
import com.yami.shop.bean.app.param.PayParam;
import com.yami.shop.bean.dto.PayDto;
import com.yami.shop.bean.enums.OrderStatus;
import com.yami.shop.bean.model.Order;
import com.yami.shop.bean.pay.PayInfoDto;
import com.yami.shop.security.api.model.YamiUser;
import com.yami.shop.security.api.util.SecurityUtils;
import com.yami.shop.service.OrderService;
import com.yami.shop.service.PayService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import com.yami.shop.common.response.ServerResponseEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE;

/**
 * @author lanhai
 */
@RestController
@RequestMapping("/p/order")
@Tag(name = "订单接口")
@Slf4j
public class PayController {


    @Autowired
    private  PayService payService;



    @Autowired
    @Lazy
    private WeixinConfig weixinConfig;
    @Autowired
    @Lazy
    @Qualifier("jsapiService")
    private JsapiService jsapiService;
    @Autowired
    @Lazy
    @Qualifier("nativePayService")
    private NativePayService nativePayService;

    @Autowired
    @Lazy
    private RefundService refundService;
    @Autowired
    private HttpServletRequest request;

    @Autowired
    @Lazy
    @Qualifier(value = "wxNotificationParser")
    private NotificationParser parser;

    @Autowired
    @Lazy
    @Qualifier(value = "h5PayService")
    private H5Service h5Service;



    @Autowired
    private WeixinUtils weixinUtils;

    @Value("${wx.pay.notify_url}")
    private String notifyUrl;

    @Value("${wx.pay.refund_notify_url}")
    private String refundNotifyUrl;

    @Autowired
    private OrderService orderService;


    /**PayDto
     * 订单支付
     */
    @PostMapping(value = "/order")
    public JSONObject wxOrderPay(@RequestBody JSONObject jsonobj, @RequestHeader("Authorization") String token) throws Exception {
        String orderNum = jsonobj.getString("orderNum");
        Order order = orderService.lambdaQuery().eq(Order::getOrderNumber, orderNum).one();
        if (Objects.isNull(order)) {
            throw new RuntimeException("订单不存在");
        }
//        if (!userId.equals(order.getUserId())) {
//            throw new RuntimeException("异常订单");
//        }
        //商品描述
        String body = jsonobj.getString("body");
        String wxCode = jsonobj.getString("wxCode");
        String openId = jsonobj.getString("open_id");

        // 如果没有open_id但有wx_code，则通过wx_code获取open_id
        if (openId == null || openId.isEmpty()) {
            if (wxCode == null || wxCode.isEmpty()) {
                throw new RuntimeException("open_id和wx_code不能同时为空");
            }
            openId = weixinUtils.getOpenIdByCode(wxCode);
        }

        // 生成订单
        PayDto payDto = new PayDto();
        payDto.setAmount(new BigDecimal(order.getActualTotal()));
        payDto.setTitle("服务订单");
        payDto.setPayNo(order.getOrderNumber());
        payDto.setOpenid(openId);
        // 支付方式很多，这里介绍 native 和 jsapiPay 两种方式
        return jsapiPay(payDto);
    }

    /**
     * 订单退款
     */
    @PostMapping(value = "/refund")
    public JSONObject wxOrderRefund(@RequestBody JSONObject jsonobj ,@RequestHeader("Authorization") String token) {

        String orderNum = jsonobj.getString("orderNum");
        Order order = orderService.lambdaQuery().eq(Order::getOrderNumber, orderNum).one();
        if (Objects.isNull(order)) {
            throw new RuntimeException("订单不存在");
        }


        String refundReason = jsonobj.getString("refundReason");

        PayDto payDto = new PayDto();
        payDto.setPayNo(orderNum);
        payDto.setRefundNo("refund_no_" + payDto.getPayNo());
        payDto.setAmount(new BigDecimal(order.getActualTotal()));

        CreateRequest createRefund = new CreateRequest();
        // createRefund.setSubMchid(weixinConfig.getMchId());
        // 原交易单和本次退款单号
        createRefund.setOutTradeNo(payDto.getPayNo());
        Order byOrderNo = orderService.lambdaQuery().eq(Order::getOrderNumber, payDto.getPayNo()).one();
        createRefund.setOutRefundNo(payDto.getRefundNo());
        createRefund.setReason(refundReason);
        // 退款地址
        String refundUrl = refundNotifyUrl;
        createRefund.setNotifyUrl(refundUrl);
        AmountReq amountReq = new AmountReq();
        amountReq.setCurrency("CNY");
        amountReq.setTotal((long) transYu2Fen(payDto.getAmount()));
        amountReq.setRefund((long) transYu2Fen(payDto.getAmount()));
        createRefund.setAmount(amountReq);
        createRefund.setFundsAccount(ReqFundsAccount.AVAILABLE);
        log.info("refund request is {}", JSONObject.toJSONString(createRefund));
        Refund refund = refundService.create(createRefund);
        log.info("退款响应 {}", JSONObject.toJSONString(refund));
        if (Objects.nonNull(refund)) {
            if (refund.getStatus().equals(Status.PROCESSING)) {
                //退款中 对应的值 Order..REFUNDING.getCode()  Order.orderStatus.REFUNDING.getCode()
                // todo zyh
                byOrderNo.setStatus(null);
                orderService.saveOrUpdate(byOrderNo);
                JSONObject data = new JSONObject();
                data.put("order_no", payDto.getPayNo());
                data.put("refund_no", refund.getOutRefundNo());
                data.put("refund_amount", payDto.getAmount());
                data.put("refund_status", "REFUND_PROCESSING");
                data.put("refund_time", refund.getCreateTime());
                data.put("refund_reason", refundReason);
                JSONObject result = new JSONObject();
                result.put("success", true);
                result.put("message", "申请退款成功");
                result.put("data", data);
                return result;
            }
        }
        JSONObject data = new JSONObject();
        data.put("success", false);
        data.put("error", "申请退款失败");
        return data;
    }


    // jsapi 支付场景
    public JSONObject jsapiPay(PayDto payDto) throws Exception {

        String openId = payDto.getOpenid();
        PrepayRequest request = new PrepayRequest();
        com.wechat.pay.java.service.payments.jsapi.model.Amount amount = new com.wechat.pay.java.service.payments.jsapi.model.Amount();
        amount.setTotal(transYu2Fen(payDto.getAmount()));
        amount.setCurrency("CNY");
        request.setAmount(amount);
        request.setAppid(weixinConfig.getAppid());
        request.setMchid(weixinConfig.getMchId());
        request.setDescription(payDto.getTitle());
        // 添加附加参数 附加数据
        request.setAttach("order-tag");
        // /api/v1/weixin/order/notify
        request.setNotifyUrl(notifyUrl);
        // http://gb3f3568.natappfree.cc
//        request.setNotifyUrl("http://gb3f3568.natappfree.cc/api/v1/pay/order/notify");
        request.setOutTradeNo(payDto.getPayNo());
        request.setDescription(payDto.getTitle());
        Payer payer = new Payer();
        payer.setOpenid(openId);
        request.setPayer(payer);
        SceneInfo sceneInfo = new SceneInfo();
        sceneInfo.setPayerClientIp(getClientIp());
        request.setSceneInfo(sceneInfo);
        PrepayResponse prepay = jsapiService.prepay(request);
        log.info("repay is {}", JSONObject.toJSONString(prepay));
        String prepayId = prepay.getPrepayId();


        //生成签名
        Long timestamp = System.currentTimeMillis() / 1000;
        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
        String sign = weixinConfig.jsApiPaySign(String.valueOf(timestamp), nonceStr, prepayId);
        JSONObject payParams = new JSONObject();
        payParams.put("timeStamp", String.valueOf(timestamp));
        payParams.put("nonceStr", nonceStr);
        payParams.put("package", "prepay_id=" + prepayId);
        payParams.put("signType", "RSA");
        payParams.put("paySign", sign);

        JSONObject data = new JSONObject();
        data.put("order_no", payDto.getPayNo());
        data.put("prepay_id", prepayId);
        data.put("pay_params", payParams);
        data.put("expire_time", timestamp + 900); // 30分钟后过期
        data.put("amount", payDto.getAmount().doubleValue());
        data.put("amount_yuan", payDto.getAmount().setScale(2, BigDecimal.ROUND_HALF_UP).toString());

        JSONObject result = new JSONObject();
        result.put("success", true);
        result.put("message", "支付参数生成成功");
        result.put("data", data);
        log.info("支付参数生成结果: {}", result);
        return result;
    }

    // native 支付场景
    private String nativePay(PayDto payDto) {
        com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request = new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();
        request.setAppid(weixinConfig.getAppid());
        request.setMchid(weixinConfig.getMchId());
        request.setDescription(payDto.getTitle());
        // 添加附加参数 附加数据
        request.setAttach("order-tag");
        request.setOutTradeNo("out_trade_no");
        // 时间格式化
        request.setNotifyUrl("https://pay.meizhuankou.top/api/v1/pay/order/notify");
        com.wechat.pay.java.service.payments.nativepay.model.Amount amount = new com.wechat.pay.java.service.payments.nativepay.model.Amount();
        amount.setCurrency("CNY");
        amount.setTotal(transYu2Fen(payDto.getAmount()));
        request.setAmount(amount);
        com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse prepay = nativePayService.prepay(request);
        String codeUrl = prepay.getCodeUrl();
        log.info("codeUrl {}", codeUrl);
        String property = System.getProperty("user.dir");
        String idStr = IdGenUtils.getIdStr();
        String path = property + "/" + idStr + ".jpg";
        // 配置二维码参数
        return path;
    }

    /**
     * 转换为 分
     */
    private Integer transYu2Fen(BigDecimal amount) {
        BigDecimal mul = NumberUtil.mul(amount, 100);
        return mul.intValue();

    }


    public String getClientIp() {
        String xff = request.getHeader("X-Real-IP");
        if (xff != null) {
            return xff;
        }
        xff = request.getHeader("x-forwarded-for");
        if (xff == null) {
            return "8.8.8.8";
        }
        return xff;
    }


    private final static String WECHAT_PAY_SIGNATURE_TYPE = "Wechatpay-Signature-Type";

    // 组装请求对象
    private static com.wechat.pay.java.core.notification.RequestParam getRequestParam(HttpServletRequest request, String body) {
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        String serial = request.getHeader(WECHAT_PAY_SERIAL);
        String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
        String signType = request.getHeader(WECHAT_PAY_SIGNATURE_TYPE);

        com.wechat.pay.java.core.notification.RequestParam param = new com.wechat.pay.java.core.notification.RequestParam.Builder()
                .serialNumber(serial)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .signType(signType)
                .body(body)
                .build();
        return param;
    }

    //     微信支付回调
    @RequestMapping(value = "/order/notify")
    public String orderNotify(HttpServletRequest request, HttpServletResponse response) {

        Gson gson = new Gson();
        // 构造应答对象
        Map<String, String> map = new HashMap<>();
        try {
            // 1.处理通知参数
            String body = HttpUtils.readData(request);
            log.info("支付通知的完整数据:{}", body);
            com.wechat.pay.java.core.notification.RequestParam param = getRequestParam(request, body);
            log.info("支付验签参数 {}", param);
            Transaction transaction = parser.parse(param, Transaction.class);
            log.info("验签成功，支付回调结果为 {}", JSONObject.toJSONString(transaction));
            // 支付金额 流水号 支付状态 透传 attach
            TransactionAmount amount = transaction.getAmount();
            String transactionId = transaction.getTransactionId();
            Transaction.TradeStateEnum tradeState = transaction.getTradeState();
            if (tradeState.equals(Transaction.TradeStateEnum.SUCCESS)) {
                log.info("支付成功，修改订单状态已支付{}", JSONObject.toJSONString(transaction));
                // 根据内部订单号更新order settlement
                payService.paySuccess(transaction.getOutTradeNo(), "");
            }
        } catch (Exception e) {
            log.info("支付验签 err {} ", e.getMessage(), e);
            return buildXmlResponse("FALL", "OK");
        }
        // 返回响应给微信服务器
        return buildXmlResponse("SUCCESS", "OK");
    }

    //    // 微信退款回调
    @RequestMapping(value = "/refund/notify")
    public String refundNotify(HttpServletRequest request, HttpServletResponse response) {

        Gson gson = new Gson();
        // 构造应答对象
        Map<String, String> map = new HashMap<>();
        try {
            // 1.处理通知参数
            String body = HttpUtils.readData(request);
            log.info("退款通知的完整数据:{}", body);
            RequestParam param = getRequestParam(request, body);
            log.info("退款验签参数 {}", param);
            RefundNotification refund = parser.parse(param, RefundNotification.class);
            log.info("验签成功，退款回调结果为 {}", JSONObject.toJSONString(refund));
            // 处理退款回调 回调金额，退款单id 退款单号 原交易单号 退款状态
            com.wechat.pay.java.service.refund.model.Amount amount = refund.getAmount();
            String refundId = refund.getRefundId();
            String outRefundNo = refund.getOutRefundNo();
            String outTradeNo = refund.getOutTradeNo();
            Status refundStatus = refund.getRefundStatus();
            Order refundOrder = orderService.lambdaQuery().eq(Order::getOrderNumber, outTradeNo).one();
            if (refundStatus.equals(Status.SUCCESS)) {
                refundOrder.setRefundSts(2);
                refundOrder.setStatus(OrderStatus.CANCEL.value());
                orderService.saveOrUpdate(refundOrder);
            }
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return gson.toJson(map);
        } catch (Exception e) {
            log.info("error is {}", e.getMessage(), e);
            // 测试错误应答
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "系统错误");
            return gson.toJson(map);
        }
    }

    private String buildXmlResponse(String returnCode, String returnMsg) {
        return "<xml><return_code><![CDATA[" + returnCode + "]]></return_code><return_msg><![CDATA[" + returnMsg + "]]></return_msg></xml>";
    }

    private String base64EncodeToken(String token){
        return Base64.getEncoder().encodeToString(token.getBytes());
    }

    /**
     * 支付接口
     */
    @PostMapping("/pay")
    @Operation(summary = "根据订单号进行支付" , description = "根据订单号进行支付")
    @Transactional
    public ServerResponseEntity<JSONObject> pay(@RequestBody PayParam payParam) throws Exception {
        YamiUser user = SecurityUtils.getUser();
        String userId = user.getUserId();


        PayInfoDto payInfo = payService.pay(userId, payParam);

        //商品描述
        String wxCode = payParam.getWxCode();
        String openId = weixinUtils.getOpenIdByCode(wxCode);

        // 生成订单
        PayDto payDto = new PayDto();
        payDto.setAmount(new BigDecimal(payInfo.getPayAmount()));
        payDto.setTitle("检测费用");
        payDto.setPayNo(payInfo.getOrderNumber());
        payDto.setOpenid(openId);
        // 支付方式很多，这里介绍 native 和 jsapiPay 两种方式
        JSONObject jsonObject = jsapiPay(payDto);
        if (jsonObject.getString("message").equals("支付参数生成成功")) {
            //设置支付中
            payService.paying(payInfo.getPayNo(), "");
        }else {
            throw new RuntimeException("支付异常,请联系管理员");
        }
        return ServerResponseEntity.success(jsonObject);
    }

    /**
     * 普通支付接口
     */
    @PostMapping("/normalPay")
    @Operation(summary = "根据订单号进行支付" , description = "根据订单号进行支付")
    public ServerResponseEntity<Boolean> normalPay(@RequestBody PayParam payParam) {

        YamiUser user = SecurityUtils.getUser();
        String userId = user.getUserId();
        PayInfoDto pay = payService.pay(userId, payParam);

        // 根据内部订单号更新order settlement
        payService.paySuccess(pay.getPayNo(), "");

        return ServerResponseEntity.success(true);
    }
}
